using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

using NBusiness.CodeDom;
using NBusiness.CodeDom.Compiler;
using NBusiness.Templates;
using Microsoft.VisualBasic;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting;

namespace NBusiness.ESharp.Compiler
{
    [Serializable]
	internal class ESharpCompiler : EntityCompiler
	{
        #region Compile Assembly from files
        public override CompilerResults CompileAssemblyFromFiles(EntityCompilerParameters options, params EntityResourceFile[] files)
        {
            CompilerResults results = new CompilerResults(new TempFileCollection());

            ESharpParser p = new ESharpParser();
            p.ParserError += new EventHandler<EntityBuildErrorEventArgs>(compilation_GenerationError);

            Dictionary<string, EntityResourceFile> eSharpFiles = new Dictionary<string, EntityResourceFile>();
            Dictionary<string, EntityResourceFile> otherFiles = new Dictionary<string, EntityResourceFile>();

            string ext = "." + Properties.Resources.ESharpFileExtension;
            foreach (EntityResourceFile file in files)
            {
                if (Path.GetExtension(file.FileName) == ext)
                {
                    eSharpFiles[file.FileName] = file;
                }
                else otherFiles[file.FileName] = file;
            }

            EntityResourceFile[] uniqueFiles = new EntityResourceFile[eSharpFiles.Count];
            eSharpFiles.Values.CopyTo(uniqueFiles, 0);

            EntityRoot root = p.ParseFiles(EntityResourceFile.GetCompileFiles(uniqueFiles));
            if (_errors.Count == 0)
            {
                uniqueFiles = new EntityResourceFile[otherFiles.Count];
                otherFiles.Values.CopyTo(uniqueFiles, 0);

                EntityFamily[] families = new EntityFamily[root.Families.Count];
                root.Families.Values.CopyTo(families, 0);
                results = CompileAssemblyFromFamilies(options, root, uniqueFiles);
            }

            results.Errors.AddRange(_errors.ToArray());

            return results;
        }
 
        #endregion

        #region Compile Assembly from families
		public override CompilerResults CompileAssemblyFromFamilies(EntityCompilerParameters options, EntityRoot root, params EntityResourceFile[] userCode)
        {
            CompilerResults results = new CompilerResults(new TempFileCollection());
            
            // Generate the various output files
            List<EntityResourceFile> files = new List<EntityResourceFile>();
            foreach (EntityTemplate t in root.Templates.Values)
            {
                t.BuildError += new EventHandler<EntityBuildErrorEventArgs>(compilation_GenerationError);
                try
                {
                    files.AddRange(t.Generate(options.PartialLanguage));
                }
                finally
                {
                    t.BuildError -= new EventHandler<EntityBuildErrorEventArgs>(compilation_GenerationError);
                }
            }
                
            files.AddRange(userCode);
            options.EmbeddedResources.AddRange(EntityResourceFile.GetEmbeddedFiles(files.ToArray()));

            string outDir = (string.IsNullOrEmpty(options.OutputAssembly) ?
                Path.GetTempPath() :
                Path.GetDirectoryName(options.OutputAssembly));

            outDir = Path.Combine(outDir,
                (string.IsNullOrEmpty(options.GeneratedPath) ?
                Path.GetFileNameWithoutExtension(Path.GetTempFileName()) :
                options.GeneratedPath));

            List<EntityResourceFile> _missing = new List<EntityResourceFile>();
            foreach (EntityResourceFile file in files)
            {
                //Non-template generated files will not have the data stored in them
                //No need to write them to disk since they already are on disk!
                if (file.Data != null)
                {
                    FileStream fs = null;
                    string path = outDir + file.FileName;
                    file.FileName = Path.GetFullPath(path);

                    if (!Directory.Exists(Path.GetDirectoryName(path)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(path));
                    }

                    if (!File.Exists(path))
                    {
                        fs = File.Create(path);
                    }
                    else
                    {
                        //If the old file is longer then the extra parts will hang around 
                        //so delete the file first.
                        File.Delete(path);

                        fs = File.OpenWrite(path);
                    }

                    using (fs)
                    {
                        fs.Write(file.Data, 0, file.Data.Length);
                    }
                }
                
                // The C# compiler expects full paths so convert relative paths into full paths.
                file.FileName = Path.GetFullPath(file.FileName);

                // Notify user of missing files.
                if (!File.Exists(file.FileName))
                {
                    ESharpBuildError err = new ESharpBuildError(ESharpErrorCode.ES0009, new EntityElement("", 0, 0, 0, 0));
                    err.Element.Path = file.FileName;
                    AddError(err);
                    _missing.Add(file);
                }
            }

            //Try to compile with the missing files anyway.
			foreach (EntityResourceFile file in _missing)
			{
                files.Remove(file);
			}

            CodeDomProvider provider = null;
            switch (options.PartialLanguage)
            {
                case PartialLanguage.CS:
                    {
                        provider = new CSharpCodeProvider();
                        break;
                    }
                case PartialLanguage.VB:
                    {
                        provider = new VBCodeProvider();
                        break;
                    }
            }

            results = provider.CompileAssemblyFromFile(
                options,
                EntityResourceFile.GetCompileFiles(files.ToArray()));             

			return results;
        }

        #endregion

        #region Error Handling
        private List<CompilerError> _errors = new List<CompilerError>();

        void compilation_GenerationError(object sender, EntityBuildErrorEventArgs e)
        {
            AddError(e.Error);
        }
        
        private void AddError(EntityBuildError error)
        {
            if (error == null)
                throw new ArgumentNullException("error");

            CompilerError compilerError = new CompilerError();
            compilerError.ErrorText = error.Message;
            if (error.Element != null)
            {
                compilerError.ErrorNumber = error.ErrorCode.ToString();
                compilerError.Line = error.Element.Line + 1;
                compilerError.Column = error.Element.Column;
                compilerError.FileName = error.Element.Path;
            }

            if (error.Type == EntityBuildErrorType.Warning)
            {
                compilerError.IsWarning = true;
            }

            lock (_errors)
            {
                _errors.Add(compilerError);
            }
        }
        #endregion

        public override CompilerResults CompileAssemblyFromDom(CompilerParameters options, System.CodeDom.CodeCompileUnit compilationUnit)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override CompilerResults CompileAssemblyFromDomBatch(CompilerParameters options, System.CodeDom.CodeCompileUnit[] compilationUnits)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override CompilerResults CompileAssemblyFromFile(CompilerParameters options, string fileName)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override CompilerResults CompileAssemblyFromFileBatch(CompilerParameters options, string[] fileNames)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override CompilerResults CompileAssemblyFromSource(CompilerParameters options, string source)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override CompilerResults CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
